package com.xzc.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ramostear.captcha.HappyCaptcha;
import com.ramostear.captcha.common.Fonts;
import com.ramostear.captcha.support.CaptchaStyle;
import com.ramostear.captcha.support.CaptchaType;
import com.xzc.seckill.config.AccessLimit;
import com.xzc.seckill.pojo.Order;
import com.xzc.seckill.pojo.SeckillMessage;
import com.xzc.seckill.pojo.SeckillOrder;
import com.xzc.seckill.pojo.User;
import com.xzc.seckill.rabiitmq.MQSenderMessage;
import com.xzc.seckill.service.GoodsService;
import com.xzc.seckill.service.OrderService;
import com.xzc.seckill.service.SeckillOrderService;
import com.xzc.seckill.vo.GoodsVo;
import com.xzc.seckill.vo.RespBean;
import com.xzc.seckill.vo.RespBeanEnum;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author 徐生
 * @version 1.0
 * Date 2024/4/6 22:31
 * Description: SeckillController
 */
@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {
    //注入需要的组件或对象
    @Autowired
    private GoodsService goodsService;
    @Autowired
    private SeckillOrderService seckillOrderService;
    @Autowired
    private OrderService orderService;
    @Autowired
    private RedisTemplate redisTemplate;
    //定义map——记录秒杀商品是否还有库存
    private Map<Long, Boolean> entryStockMap = new HashMap<>();
    @Autowired
    private MQSenderMessage mqSenderMessage;//装配消息的生产者/发送者


    //该方法是在SeckillController类的所有属性，都是初始化后，自动执行的
    //这里我们就可以将所有秒杀商品的库存量，加载到redis

    /**
     * 处理用户抢购请求/秒杀
     * 说明：先完成一个Version 1.0版本，后面在高并发情况下，再做优化
     */
    /*@RequestMapping("/doSeckill")
    public String doSeckill(Model model, User user, Long goodsId){
        if (user == null) return "login";

        //将user放入到model，下一个模板可以使用
        model.addAttribute("user", user);
        //获取到goodsVo
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);

        //判断库存余量
        if (goodsVo.getStockCount() < 1){//没有库存
            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
            return "secKillFail";//错误页面
        }
        //判断用户是否重复购买——判断当前购买用户id和购买商品id是否已经在 商品描述表存在了(t_seckill_order)
        SeckillOrder seckillOrder = seckillOrderService.getOne(
                new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));

        if (seckillOrder != null){//商品已经存在，说明已经复购
            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
            return "secKillFail";//错误页面
        }

        //抢购
        Order order = orderService.seckill(user, goodsVo);
        if (order == null){
            model.addAttribute("errmsg", "秒杀失败");
            return "secKillFail";//错误页面
        }

        //进入到订单页
        model.addAttribute("order",order);
        model.addAttribute("goods",goodsVo);

        System.out.println("-----秒杀Version 1.0-----");

        return "orderDetail";
    }*/


    //Version 2.0版本，解决高并发情况下的复购和超卖问题
    //Version 6.0版本，加入秒杀安全机制，直接返回RespBean
    @RequestMapping("/{path}/doSeckill")
    @ResponseBody
    public RespBean doSeckill(@PathVariable String path, Model model, User user, Long goodsId){
        if (user == null) /*return "login"*/ return RespBean.error(RespBeanEnum.SESSION_ERROR);

        //这里我们增加一个判断逻辑，校验用户携带的path是否正确
        boolean b = orderService.checkPath(user, goodsId, path);
        if (!b){//校验失败
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }

        //将user放入到model，下一个模板可以使用
//        model.addAttribute("user", user);
        //获取到goodsVo
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);

        //判断库存余量
        if (goodsVo.getStockCount() < 1){//没有库存
//            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
//            return "secKillFail";//错误页面
            return RespBean.error(RespBeanEnum.ENTRY_STOCK);
        }


        //判断用户是否重复购买——判断当前购买用户id和购买商品id是否已经在 商品描述表存在了(t_seckill_order)
        /*SeckillOrder seckillOrder = seckillOrderService.getOne(
                new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));

        if (seckillOrder != null){//商品已经存在，说明已经复购
            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
            return "secKillFail";//错误页面
        }*/
        //判断用户是否重复购买——直接到redis中获取对应的秒杀订单，有则说明已经抢购了
        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsVo.getId());
        if (seckillOrder != null){//说明该用户已经抢购了该商品
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
//            return "secKillFail";//错误页面
            return RespBean.error(RespBeanEnum.REPEAT_ERROR);
        }

        //Version 4.0版本，加入内存标记优化秒杀
        //对map进行判断[内存标记]，如果商品在map已经标记为没有库存，则直接返回，无需进行redis预减
        if(entryStockMap.get(goodsId)){
//            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
//            return "secKillFail";//错误页面
            return RespBean.error(RespBeanEnum.ENTRY_STOCK);
        }

        //Version 3.0版本，redis库存预减
        //库存预减，如果在redis中预减库存，发现秒杀商品已经没有了，就直接返回
        //从而减少执行 orderService.seckill() 请求，防止线程堆积，优化秒杀/高并发
        //提示：decrement()具有原子性，并且返回当前key对应value-1的值
        Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
        if (decrement < 0){//说明这个商品已经没有库存

            //说明当前秒杀的商品，已经没有库存，设置值为true方便后续操作
            entryStockMap.put(goodsId,true);

            //decrement会一直-1，为了美观 恢复库存为0（可用不用）
            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
//            return "secKillFail";//错误页面
            return RespBean.error(RespBeanEnum.REPEAT_ERROR);
        }


       /* //抢购
        Order order = orderService.seckill(user, goodsVo);
        if (order == null){
            model.addAttribute("errmsg", "秒杀失败");
            return "secKillFail";//错误页面
        }

        //进入到订单页
        model.addAttribute("order",order);
        model.addAttribute("goods",goodsVo);
        return "orderDetail";*/

        //Version 5.0版本，加入消息队列
        //抢购,向消息队列发送秒杀请求,实现了秒杀异步请求
        //这里我们发送秒杀消息后，立即快速返回结果[临时结果] - "比如排队中.."
        //客户端可以通过轮询，获取到最终结果
        //创建SeckillMessage
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        mqSenderMessage.sendSeckillMessage(JSONUtil.toJsonStr(seckillMessage));
//        model.addAttribute("errmsg","排队中...");
//        return "secKillFail";
//        return RespBean.error(RespBeanEnum.SEK_KILL_WAIT);
        return RespBean.success(RespBeanEnum.SEK_KILL_WAIT);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        //查询所有的秒杀商品
        List<GoodsVo> list = goodsService.findGoodsVo();
        //先判断是否为空
        if (CollectionUtils.isEmpty(list)) return;
        //遍历List，然后将秒杀商品的库存量，放入到redis
        //秒杀商品库存量对应key：seckillGoods:商品id
        list.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("seckillGoods:"+goodsVo.getId(),goodsVo.getStockCount());
            //初始化map
            //goodsId：false表示有库存    true表示没有库存
            entryStockMap.put(goodsVo.getId(),false);
        });
    }

    //获取秒杀路径
    @RequestMapping("/path")
    @ResponseBody
    /**
     * 解读：@AccessLimit(second = 5,maxCount = 5,needLogin = true)
     * 1. 使用注解的方式完成对用户的限流防刷——通用性、灵活性提高
     * 2. second=5, maxCount=5 说明在5秒内可以访问的最大次数是5次
     * 3. needLogin=true 表示用户是否需要登录
     */
    @AccessLimit(second = 5,maxCount = 5,needLogin = true)
    public RespBean getSeckillPath(User user, Long goodsId, String captcha, HttpServletRequest request){
        if (user==null || goodsId<0 || !StringUtils.hasText(captcha))
            return RespBean.error(RespBeanEnum.SESSION_ERROR);

        //增加业务逻辑：加入redis计数器，完成对用户的限流防刷
        //比如：5秒内访问次数超过了5次，就认为是刷接口
        //这里先把代码写在方法中，后面使用注解提高使用的通用性
//        StringBuffer url = request.getRequestURL();//uri就是 localhost:8080/seckill/path 的/seckill/path路径
//        ValueOperations valueOperations = redisTemplate.opsForValue();
//        String key = url + ":" + user.getId();
//        Integer count = (Integer) valueOperations.get(key);
//        if (count == null){//说明还没key，就初始化，值为1，过期时间为5秒
//            valueOperations.set(key,1,5, TimeUnit.SECONDS);
//        } else if (count < 5) {//说明正常访问
//            valueOperations.increment(key);
//        } else {//说明用户在刷接口
//            return RespBean.error(RespBeanEnum.ACCESS_LIMIT_REACHED);
//        }

        //增加一个业务逻辑——校验用户输入的验证码是否正确
        boolean check = orderService.checkCaptcha(user, goodsId, captcha);
        if (!check){//如果校验失败
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }

        String path = orderService.createPath(user, goodsId);
        return RespBean.success(path);
    }


    //生成验证码-happyCaptcha
    @RequestMapping("/captcha")
    public void happyCaptcha(HttpServletRequest request, HttpServletResponse response, User user, Long goodsId) {
        //生成验证码，并输出
        //注意，该验证码，默认就保存到session中, key是 happy-captcha
        HappyCaptcha.require(request, response)
                .style(CaptchaStyle.ANIM)               //设置展现样式为动画
                .type(CaptchaType.NUMBER)               //设置验证码内容为数字
                .length(6)                              //设置字符长度为6
                .width(220)                             //设置动画宽度为220
                .height(80)                             //设置动画高度为80
                .font(Fonts.getInstance().zhFont())     //设置汉字的字体
                .build().finish();                      //生成并输出验证码

        //把验证码的值，保存Redis [考虑项目分布式], 设置了验证码的失效时间100s
        //key: captcha:userId:goodsId
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId
                , (String) request.getSession().getAttribute("happy-captcha"), 100, TimeUnit.SECONDS);
    }

}
